/*
 *      Copyright (c) 2007 Marcelo S. Araujo
 *      All rights reserved.
 *
 *      Redistribution and use in source and binary forms, with or without
 *      modification, are permitted provided that the following conditions
 *      are met:
 *      1. Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *      2. Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *
 *      THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 *      ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 *      IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 *      ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 *      FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 *      DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 *      OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 *      HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 *      LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 *      OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 *      SUCH DAMAGE.
 *
 */


ParseFile FileConfs;
static string CvsAlias = "cvs -d " + FileConfs.USERNAME + "@" + FileConfs.CVSHOST + ":/home/pcvs/ ";

void Cvs_Diff(string PORTNAME);
void Cvs_Update(string PORTNAME);
void Cvs_CheckOut(string PORTNAME);
void Cvs_Commit_Check(string PORTNAME);
void Cvs_Commit(string PORTNAME);
void Cvs_GetLocalPatch(string PORTNAME);
void Cvs_AddPort(string PORTNAME, string CATEGORY);
void Cvs_CVSROOT(string PORTNAME, string CATEGORY, int CONTROL);
void Cvs_RepoCopy(string OLDPORTNAME, string NEWPORTNAME);
void Cvs_ForceCommit(string PORTNAME, string CATEGORY, string TempOLD, string TempNEW);
void Cvs_RemovePort(string PORTNAME, string CATEGORY);
void Cvs_Templates(string PORTNAME, string CATEGORY, int TYPE); // TYPE (1/CVSROOT) (2/NEWPORT) (3/<...>)

class Modules {
	public:
	void ModulesAdd(string PORTNAME, string CATEGORY);
	void ModulesRm(string PORTNAME, string CATEGORY);
};

void Modules::ModulesAdd(string PORTNAME, string CATEGORY) {
	int BROKEN = 0;
	string ModulePath = "ports/" + CATEGORY + "/Makefile";
	string ModulePaths = "ports/" + CATEGORY + "/Makefile.new";
	string MoveModules = "mv ports/" + CATEGORY + "/Makefile.new" + " ports/" + CATEGORY + "/Makefile";
	string line;
	vector<string> line_order;
	vector<string> header;
	vector<string> foobar;
	vector<string> comment;
	vector<string>::iterator it;
	ifstream ReadPort(ModulePath.c_str());
	while(!ReadPort.eof()) {
		getline(ReadPort, line);
		int FoundAlph = line.find("#");
		int FoundAlph2 = line.find(".include");
		int FoundAlph3 = line.find("COMMENT");
		int FoundAlph4 = line.find(PORTNAME);
		if(FoundAlph != string::npos) {
			header.push_back(line);
		} else if(FoundAlph2 != string::npos) {
			foobar.push_back(line);
		} else if(FoundAlph3 != string::npos) {
			comment.push_back(line);
		} else if(FoundAlph4 != string::npos) {
			BROKEN = 1;
		} else {
			line_order.push_back(line);
		}
	}
	string InsMod = "    SUBDIR += " + PORTNAME;
	if(BROKEN != 1) {
		ofstream WritePort(ModulePaths.c_str());
		line_order.push_back(InsMod);
		// Insert Header.
		for(it = header.begin(); it != header.end(); ++it) {
			WritePort << *it << endl;
		}
		// Insert Comment.
		for(it = comment.begin(); it != comment.end(); ++it) {
			WritePort << endl;
			WritePort << *it << endl;
			WritePort << endl;
		}
		// Alphabetic order :-)
		sort(line_order.begin(), line_order.end());
		for(it = line_order.begin(); it != line_order.end(); ++it) {
			if(*it != "\0") {
				WritePort << *it << endl;
				}
		}
		// Insert FooBar :-)
		for(it = foobar.begin(); it != foobar.end(); ++it) {
			WritePort << endl;
			WritePort << *it << endl;
		}
		WritePort.close();
		// Moron move file.
		system(MoveModules.c_str());
	} else {
		cout << "==> Modules has PORTNAME...." << endl;
	}
	static int CONTROL = 0;
	Cvs_Templates(PORTNAME, CATEGORY, 2); // TYPE (1/CVSROOT) (2/NEWPORT) (3/<...>)
	Cvs_CVSROOT(PORTNAME, CATEGORY, CONTROL);
};

void Modules::ModulesRm(string PORTNAME, string CATEGORY) {
	int CONTROL = 1;
	string ModulePath = "ports/" + CATEGORY + "/Makefile";
	string ModulePaths = "ports/" + CATEGORY + "/Makefile.new";
	string MoveModules = "mv ports/" + CATEGORY + "/Makefile.new" + " ports/" + CATEGORY + "/Makefile";
	string line;
	int FoundPortname = 0;
	ifstream ReadPort(ModulePath.c_str());
	ofstream WritePort(ModulePaths.c_str());
	while (!ReadPort.eof()) {
		getline(ReadPort, line);
		int FoundAlph = line.find(".include");
		FoundPortname = line.find(PORTNAME);
		if(FoundPortname == string::npos && FoundAlph == string::npos) {
			WritePort << line << endl;
		} else if(FoundAlph != string::npos) {
			WritePort << line;
		}
	}
	WritePort.close();
	// Moron move file.
	system(MoveModules.c_str());
	Cvs_CVSROOT(PORTNAME, CATEGORY, CONTROL);
};

void Cvs_RemovePort(string PORTNAME, string CATEGORY) {
	Modules M;
	string sendstring = "find " + PORTNAME + " -name '*' -not -name 'CVS*' -not -name 'Root' -not -name 'Repository' -not -name 'Entries*' -not -name 'Template' -delete";
	cout << "==> Remove all files." << endl;
	system(sendstring.c_str());
	sendstring = CvsAlias + " co " + " ports/" + CATEGORY + "/Makefile";
	cout << "==> Remove entries from Makefile of Category." << endl;
	system(sendstring.c_str());
	sendstring = CvsAlias + "rm " + PORTNAME;
	cout << "==> Remove PORTNAME." << endl;
	system(sendstring.c_str());
	cout << "==> Remove entries from CVSROOT/modules." << endl;
	M.ModulesRm(PORTNAME, CATEGORY);
};

void Cvs_ForceCommit(string PORTNAME, string CATEGORY, string TempOLD, string TempNEW) {
	static string msg = "- Forced commit to note repocopy, from " + TempOLD + " --> " + TempNEW;
	static string sendstring = CvsAlias + " ci" + " -f " + "ports/" + CATEGORY + "/" + PORTNAME;
	string CvsTemplate = "ports/" + CATEGORY + "/" + PORTNAME + "/CVS/Template";
	string line;
	vector<string> Template;
	ifstream ReadTemp(CvsTemplate.c_str());
	Template.push_back(msg);
	while(!ReadTemp.eof()) {
		getline(ReadTemp, line);
		Template.push_back(line);
	}
	ofstream WriteTemp(CvsTemplate.c_str());
	vector<string>::iterator it;
	for( it = Template.begin(); it != Template.end(); ++it) {
		WriteTemp << *it << endl;
	}
	ReadTemp.close();
	WriteTemp.close();
	cout << "===> Forced commit." << endl;
	system(sendstring.c_str());
};

void Cvs_GetLocalPatch(string PORTNAME) {
	cout << "===> Get patch file: " << FileConfs.PATCH << PORTNAME << ".diff" << endl;
	string sendstring = "cd " + PORTNAME + "; pwd ;" + "fetch " + FileConfs.PATCH + "`basename ${PWD}.diff`";
	system(sendstring.c_str());
	cout << "===> Apply patch." << endl;
	sendstring = "cd " + PORTNAME + "; pwd ;" + "patch < `basename ${PWD}`.diff";
	system(sendstring.c_str());
	cout << "===> Remove garbage files." << endl;
	sendstring = "cd " + PORTNAME + "; find . -type f \\( -name '*.rej' -o -name '*.orig' -o -name '*.diff' \\) -print";
	system(sendstring.c_str());
	sendstring = "cd " + PORTNAME + "; find . -type f \\( -name '*.rej' -o -name '*.orig' -o -name '*.diff' \\) -delete";
	system(sendstring.c_str());
	Cvs_Update(PORTNAME);
	cout << "===> Pet portlint." << endl;
	sendstring = "cd " + PORTNAME + "; pwd ; " + "portlint -C";
	system(sendstring.c_str());
	Cvs_Diff(PORTNAME);
};

/*********************************************************************
 * Simples Check, update and pet portlint.
 *********************************************************************/

void Cvs_Commit_Check(string PORTNAME) {
	if(PORTNAME == ".") {
		Cvs_Update(PORTNAME);
		cout << "===> Pet portlint." << endl;
		string path = getcwd(NULL, 0);
		string sendstring = "cd " + path + "; portlint -C";
		system(sendstring.c_str());
	} else {
		Cvs_Update(PORTNAME);
		cout << "===> Pet portlint." << endl;
		string sendstring = "cd " + PORTNAME + "; portlint -C";
		system(sendstring.c_str());
	}
};

/*********************************************************************
 * Simples, cvs ci.
 *********************************************************************/

void Cvs_Commit(string PORTNAME) {
	if(PORTNAME == ".") {
		string sendstring = CvsAlias + "ci";
		cout << "===> Commit your work." << endl;
		system(sendstring.c_str());
	} else {
		string sendstring = CvsAlias + "ci " + PORTNAME;
		cout << "===> Commit your work." << endl;
		system(sendstring.c_str());
	}
};

/*********************************************************************
 * Simples, cvs co.
 *********************************************************************/

void Cvs_CheckOut(string PORTNAME) {
	cout << "===> Checkout " << PORTNAME << endl;
	string sendstring = CvsAlias + "co " + PORTNAME;
	system(sendstring.c_str());
};

/*********************************************************************
 * Simples, cvs update.
 *********************************************************************/

void Cvs_Update(string PORTNAME) {
	int N;
	N = 1;
	if(PORTNAME == ".") {

		string sendstring = CvsAlias + "update ";
		cout << "===> Update." << endl;
		system(sendstring.c_str());
	} else {
		string sendstring = CvsAlias + "update " + PORTNAME;
		cout << "===> Update." << endl;
		system(sendstring.c_str());
	}
};

/*********************************************************************
 * Simples, cvs diff -uN.
 *********************************************************************/

void Cvs_Diff(string PORTNAME) {
	int N;
	N = 1;
	if(PORTNAME == ".") {
		string sendstring = CvsAlias + "diff -uN >../`basename ${PWD}`.diff";
		system(sendstring.c_str());
		cout << "===> Diff file on: ../" << endl;
	} else {
		string sendstring = CvsAlias + "diff -uN " + PORTNAME + "|more";
		cout << "===> Show diff file. " << endl;
		system(sendstring.c_str());
	}
};

void Cvs_AddPort(string PORTNAME, string CATEGORY, int CONTROL) {

	Modules M;
	string sendstring = CvsAlias + " co " + "ports/" + CATEGORY + "/Makefile";
	cout << "===> Checkout Makefile CATEGORY." << endl;
	system(sendstring.c_str());
	sendstring = "mv " + PORTNAME + " ports/" + CATEGORY + "/" + PORTNAME;
	system(sendstring.c_str());
	sendstring = " cd ports/" + CATEGORY + "/ ;" + CvsAlias + " add -ko `find " + PORTNAME + " -type d | grep -v CVS`";
	cout << "===> Add directories. " << endl;
	system(sendstring.c_str());
	sendstring = " cd ports/" + CATEGORY + "/ ;" + CvsAlias + " add -ko `find " + PORTNAME + " -type f | grep -v CVS`";
	cout << "===> Add files. " << endl;
	system(sendstring.c_str());
	cout << "===> Add CVSROOT/modules." << endl;
	M.ModulesAdd(PORTNAME, CATEGORY);
};

/*********************************************************************
 * control = 0 (ADD CVSROOT/modules), control = 1 (RM CVSROOT/modules)
 *********************************************************************/
void Cvs_CVSROOT(string PORTNAME, string CATEGORY, int CONTROL) {
	string line;
	int begins, ends, line_begins, line_ends, portname_exist, BROKEN, count = 0;
	begins = ends = line_begins = line_ends = portname_exist = BROKEN = count;
	static string CVSROOT = "CVSROOT/modules";
	static string CVSROOT_NEW = "CVSROOT/modules.NEW";
	static string MoveCVSROOT = "mv " + CVSROOT_NEW +  " " +  CVSROOT;
	static string ModulesPortname = "ports/" + CATEGORY + "/" + PORTNAME;
	string sendstring = CvsAlias + "co " + CVSROOT;
	vector<string> line_header, line_body;
	vector<string>::iterator it;
	cout << "===> Checkout CVSROOT/modules" << endl;
	system(sendstring.c_str());
	ifstream ReadCVSROOT(CVSROOT.c_str());
	ifstream RFTPrint(CVSROOT.c_str());
	while(!ReadCVSROOT.eof()) {
		getline(ReadCVSROOT, line);
		begins = line.find("# The CVS Modules File");
		ends   = line.find("# Add other modules here");
		portname_exist = line.find(ModulesPortname);
		if(begins != string::npos) {
			line_begins = count;
		}
		if(ends != string::npos) {
			line_ends = count;
		}
		if(portname_exist != string::npos) {
			BROKEN = 1;
		}
		count = count + 1;
	}
	// Add PORTNAME into modules.
	if(CONTROL == 0) {
		string PortInsert = PORTNAME + "\t" + "ports/" + CATEGORY + "/" + PORTNAME;
		if(BROKEN == 0) {
			line_body.push_back(PortInsert);
			ofstream WriteCVSROOT(CVSROOT_NEW.c_str());
			for(int n = 0; n < count; n++) {
				getline(RFTPrint, line);
				if(n >= line_begins && n <= line_ends) {
				line_header.push_back(line);
				} else {
					line_body.push_back(line);
				}
			}
		// Insert Header.
			for(it = line_header.begin(); it != line_header.end(); ++it) {
				WriteCVSROOT << *it << endl;
			}
		// Insert Body with alphabetical order. :-)
			sort(line_body.begin(), line_body.end());
			for(it = line_body.begin(); it != line_body.end(); ++it) {
				if(*it != "\0") {
					WriteCVSROOT << *it << endl;
				}
			}
			WriteCVSROOT.close();
		// Moron move files.
			system(MoveCVSROOT.c_str());
			Cvs_Templates(PORTNAME, CATEGORY, 1); // TYPE (1/CVSROOT) (2/NEWPORT) (3/<...>)
		} else {
			cout << "==> CVSROOT has module name." << endl;
		}
	// Remove PORTNAME into modules.
	} else if(CONTROL == 1 && BROKEN == 1) {
		ofstream WriteCVSROOT(CVSROOT_NEW.c_str());
		for(int n = 0; n < count; n++) {
			getline(RFTPrint, line);
			int ModulesP = line.find(ModulesPortname);
			if(n >= line_begins && n <= line_ends) {
				line_header.push_back(line);
			} else {
				if(ModulesP == string::npos) {
					line_body.push_back(line);
				}
			}
		}
		for(it = line_header.begin(); it != line_header.end(); ++it) {
			WriteCVSROOT << *it << endl;
		}
		for(it = line_body.begin(); it != line_body.end(); ++it) {
			if(*it != "\0") {
				WriteCVSROOT << *it << endl;
			}
		}
		cout << "===> Port entries removed from CVSROOT/modules" << endl;
		WriteCVSROOT.close();
		// Moron move files.
		system(MoveCVSROOT.c_str());
	} else {
		cout << "==> CVSROOT/modules has the PORTNAME..." << endl;
	}
	ReadCVSROOT.close();
	RFTPrint.close();
};

// Use: ./portmenu <CATEGORY/PORTNAME> <NEWCATEGORY/NEWPORTNAME>
void Cvs_RepoCopy(string OLDPORTNAME, string NEWPORTNAME) {
	static string TempOLD = OLDPORTNAME;
	static string TempNEW = NEWPORTNAME;
	int OLDPORTNAME_Slash = OLDPORTNAME.find("/");
	int NEWPORTNAME_Slash = NEWPORTNAME.find("/");
	string OLD, NEW, sendstring;
	/*****************************************************************
	 * Get PORTNAME.
	 *****************************************************************/

	if(OLDPORTNAME_Slash != string::npos) {
		OLD = OLDPORTNAME;
		OLD.erase(0, OLDPORTNAME_Slash + 1);
		int size = OLDPORTNAME.size() - OLDPORTNAME_Slash;
		OLDPORTNAME.erase(OLDPORTNAME_Slash, size);
	}
	if(NEWPORTNAME_Slash != string::npos) {
		NEW = NEWPORTNAME;
		NEW.erase(0, NEWPORTNAME_Slash + 1);
		int size = NEWPORTNAME.size() - NEWPORTNAME_Slash;
		NEWPORTNAME.erase(NEWPORTNAME_Slash, size);
	}
	if(OLDPORTNAME == NEWPORTNAME) {
		sendstring = CvsAlias + "co ports/" + OLDPORTNAME + "/Makefile" +
						" ports/" + OLDPORTNAME + "/" + OLD + " ports/" + NEWPORTNAME + "/" + NEW;
	} else {
		sendstring = CvsAlias + "co ports/" + OLDPORTNAME + "/Makefile" +
						" ports/" + NEWPORTNAME + "/Makefile" + " ports/"
						+ OLDPORTNAME + "/" + OLD + " ports/" + NEWPORTNAME + "/" + NEW;
	}
	cout << "===> Checkout files." << endl;
	system(sendstring.c_str());
	Modules M;
	M.ModulesRm(OLD, OLDPORTNAME); // PORTNAME and CATEGORY.
	M.ModulesAdd(NEW, NEWPORTNAME); // PORTNAME and CATEGORY.
	Cvs_ForceCommit(NEW, NEWPORTNAME, TempOLD, TempNEW);
};

// TYPE (1/CVSROOT) (2/NEWPORT) (3/<...>)
void Cvs_Templates(string PORTNAME, string CATEGORY, int TYPE) {
	cout << "===> Write Template file." << endl;
	string msgT;
	string Template_Path;
	switch(TYPE) {
		case 1:
			msgT = PORTNAME + " --> ports/" + CATEGORY + "/" + PORTNAME;
			Template_Path = "CVSROOT/CVS/Template";
			break;
		case 2:
			msgT = "- New port add.";
			Template_Path = "ports/" + CATEGORY + "/" + "CVS/Template";
			break;
	}
	ifstream ReadTemp(Template_Path.c_str());
	vector<string> Template;
	vector<string>::iterator it;
	string line;
	Template.push_back(msgT);
	while(!ReadTemp.eof()) {
		getline(ReadTemp, line);
		Template.push_back(line);
	}
	ofstream WriteTemp(Template_Path.c_str());
	for(it = Template.begin(); it != Template.end(); ++it) {
		WriteTemp << *it << endl;
	}
	WriteTemp.close();
}
